import patchVnode from './patchVnode'
import createElement from './createElement'
/**
 * diff算法的优化策略：
 * 对于新的数组和旧的数组，分别提供俩个指针指向数组的开头和末尾(共四个指针)，这里称为旧前、旧后、新前、新后
 *
 * 按以下四个步骤进行比较
 * 1. 新前与旧前 ———— 命中后步骤(1.patchVnode处理相同节点；2.旧前指针后移；3.新前指针后移)
 * 2. 新后与旧后 ———— 命中后步骤(1.patchVnode处理相同节点；2.旧后指针前移；3.新后指针前移)
 * 3. 新后与旧前 ———— 命中后步骤(1.patchVnode处理相同节点；2.将新前节点移动到旧后节点的后面；3.旧前指针后移；4.新后指针前移)
 * 4. 新前与旧后 ———— 命中后步骤(1.patchVnode处理相同节点；2.将新前节点移动到旧前节点的前面；3.旧后指针前移；4.新前指针后移)
 */

export default function updateChildren(parentElm, oldCh, newCh) {
  console.log(parentElm, oldCh, newCh)
  // 旧前指针
  let oldStartIdx = 0
  // 新前指针
  let newStartIdx = 0
  // 旧后指针
  let oldEndIdx = oldCh.length - 1
  // 新后指针
  let newEndIdx = newCh.length - 1

  // 旧前节点
  let oldStartVnode = oldCh[0]
  // 旧后节点
  let oldEndVnode = oldCh[oldEndIdx]
  // 新前节点
  let newStartVnode = newCh[0]
  // 新后节点
  let newEndVnode = newCh[newEndIdx]
  // 当四种情况都未命中时，用来缓存旧节点的key值的对象
  let keyMap = null

  // 当扫描完旧数组或者新数组，结束循环
  while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    console.log('⭐')
    console.log(oldCh, newCh)
    console.table(oldStartVnode, newStartVnode, oldEndVnode, newEndVnode)
    // 1.首先要略过已经加undefined标记的项
    if (oldStartVnode == null || oldStartVnode == undefined) {
      // 旧前节点后移
      oldStartVnode = oldCh[++oldStartIdx]
    } else if (oldEndVnode == null || oldEndVnode == undefined) {
      // 旧后节点前移
      oldEndVnode = oldCh[--oldEndIdx]
    } else if (newStartVnode == null || newStartVnode == undefined) {
      // 新前节点后移
      newStartVnode = newCh[++newStartIdx]
    } else if (newEndVnode == null || newEndVnode == undefined) {
      // 新后节点前移
      newEndVnode = newCh[--newEndIdx]
    } else if (checkSameVnode(oldStartVnode, newStartVnode)) {
      // 2.再判断四种情况是否命中
      // 1.新前与旧前节点相同
      console.log('1. 新前与旧前命中')
      // 1.1 patchVnode处理相同节点
      patchVnode(oldStartVnode, newStartVnode)
      // 1.2 旧前指针后移
      oldStartVnode = oldCh[++oldStartIdx]
      // 1.3 新前指针后移
      newStartVnode = newCh[++newStartIdx]
    } else if (checkSameVnode(oldEndVnode, newEndVnode)) {
      // 2.新后与旧后节点相同
      console.log('2. 新后与旧后命中')
      // 2.1 patchVnode处理相同节点
      patchVnode(oldEndVnode, newEndVnode)
      // 2.2 旧后指针前移
      oldEndVnode = oldCh[--oldEndIdx]
      // 2.3 新后指针前移
      newEndVnode = newCh[--newEndIdx]
    } else if (checkSameVnode(oldStartVnode, newEndVnode)) {
      // 3.新后与旧前节点相同
      console.log('3. 新后与旧前命中')
      // 3.1 patchVnode处理相同节点
      patchVnode(oldStartVnode, newEndVnode)
      // 3.2 将新前节点移动到旧后节点的后面（如何移动节点？？只要你插入一个已经在DOM树上的节点，它就会被移动）
      parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling)
      // 3.3 旧前指针后移
      oldStartVnode = oldCh[++oldStartIdx]
      // 3.4 新后指针前移
      newEndVnode = newCh[--newEndIdx]
    } else if (checkSameVnode(oldEndVnode, newStartVnode)) {
      // 4.新前与旧后节点相同
      console.log('4. 新前与旧后命中')
      // 4.1 patchVnode处理相同节点
      patchVnode(oldEndVnode, newStartVnode)
      // 4.2 将新前节点移动到旧前节点的前面（如何移动节点？？只要你插入一个已经在DOM树上的节点，它就会被移动）
      parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm)
      // 4.4 旧后指针前移
      oldEndtVnode = oldCh[--oldEndIdx]
      // 4.4 新前指针后移
      newStartVnode = newCh[++newStartIdx]
    } else {
      // 以上四种情况都未命中
      // 寻找key的map
      if (!keyMap) {
        keyMap = {}
        // 遍历旧节点
        for (let i = oldStartIdx; i <= oldEndIdx; i++) {
          const key = oldCh[i].key
          if (key != undefined) {
            keyMap[key] = i
          }
        }
      }
      console.log(keyMap, 'map')
      // 寻找当前这项（newStartIdx）在keyMap中的映射的位置序号
      const idxInOld = keyMap[newStartVnode.key]
      if (idxInOld == undefined) {
        // 判断：如果idxInOld未undefined，表示他是全新的项；放在老数组的最前面
        // 被加入的项就是newStartVnode项，此时还是虚拟dom,通过createElement函数转换为真正的dom
        parentElm.insertBefore(createElement(newStartVnode), oldStartVnode.elm)
      } else {
        // 否则表示它在旧节点中有位置，需要移动到老数组的对应节点
        const elmToMove = oldCh[idxInOld]
        patchVnode(elmToMove, newStartVnode)
        // 把这项设置为undefined，表示已经处理完这项了
        oldCh[idxInOld] = undefined
        // 移动，调用insertBefore也可以实现移动，移动到旧前节点之前
        parentElm.insertBefore(elmToMove.elm, oldStartVnode.elm)
      }
      // 新数组开始节点下移
      newStartVnode = newCh[++newStartIdx]
    }
  }

  // 继续看看有没有剩余的。循环结束了start还是比old小
  if (newStartIdx <= newEndIdx) {
    console.log('新数组还有剩余节点没有处理，要加项')
    // 插入的标杆
    const before =
      newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm

    for (let i = newStartIdx; i <= newEndIdx; i++) {
      // insertBefore方法可以自动识别null,如果时null就会自动拍到队尾去。和appendChild是一致了
      // new[i]现在还没有真正的DOM，所以要调用careteElement()函数变为DOM
      parentElm.insertBefore(createElement(newCh[i]), before)
    }
  } else if (oldStartIdx <= oldEndIdx) {
    console.log('旧数组还有剩余节点没有处理，要删除项')
    // 批量删除oldStart和oldEnd指针之间的项
    for (let i = oldStartIdx; i <= oldEndIdx; i++) {
      if (oldCh[i]) {
        parentElm.removeChild(oldCh[i].elm)
      }
    }
  }
}

// 判断新旧虚拟节点是否时同一个虚拟节点
function checkSameVnode(a, b) {
  return a.sel == b.sel && a.key == b.key
}
